// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "esp32-hal-spi.h"

#if SOC_GPSPI_SUPPORTED
#include "esp32-hal.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_attr.h"
#include "soc/spi_reg.h"
#include "soc/spi_struct.h"
#include "soc/io_mux_reg.h"
#include "soc/gpio_sig_map.h"
#include "soc/rtc.h"
#include "hal/clk_gate_ll.h"
#include "esp32-hal-periman.h"

#include "esp_system.h"
#include "esp_intr_alloc.h"

#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
#include "soc/dport_reg.h"
#include "esp32/rom/ets_sys.h"
#include "esp32/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "soc/dport_reg.h"
#include "esp32s2/rom/ets_sys.h"
#include "esp32s2/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "soc/dport_reg.h"
#include "esp32s3/rom/ets_sys.h"
#include "esp32s3/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3/rom/ets_sys.h"
#include "esp32c3/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C6
#include "esp32c6/rom/ets_sys.h"
#include "esp32c6/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32H2
#include "esp32h2/rom/ets_sys.h"
#include "esp32h2/rom/gpio.h"
#else 
#error Target CONFIG_IDF_TARGET is not supported
#endif

struct spi_struct_t {
    spi_dev_t * dev;
#if !CONFIG_DISABLE_HAL_LOCKS
    SemaphoreHandle_t lock;
#endif
    uint8_t num;
    int8_t sck;
    int8_t miso;
    int8_t mosi;
    int8_t ss;
};

#if CONFIG_IDF_TARGET_ESP32S2
// ESP32S2
#define SPI_COUNT           (3)

#define SPI_CLK_IDX(p)      ((p==0)?SPICLK_OUT_MUX_IDX:((p==1)?FSPICLK_OUT_MUX_IDX:((p==2)?SPI3_CLK_OUT_MUX_IDX:0)))
#define SPI_MISO_IDX(p)     ((p==0)?SPIQ_OUT_IDX:((p==1)?FSPIQ_OUT_IDX:((p==2)?SPI3_Q_OUT_IDX:0)))
#define SPI_MOSI_IDX(p)     ((p==0)?SPID_IN_IDX:((p==1)?FSPID_IN_IDX:((p==2)?SPI3_D_IN_IDX:0)))

#define SPI_SPI_SS_IDX(n)   ((n==0)?SPICS0_OUT_IDX:((n==1)?SPICS1_OUT_IDX:0))
#define SPI_HSPI_SS_IDX(n)  ((n==0)?SPI3_CS0_OUT_IDX:((n==1)?SPI3_CS1_OUT_IDX:((n==2)?SPI3_CS2_OUT_IDX:SPI3_CS0_OUT_IDX)))
#define SPI_FSPI_SS_IDX(n)  ((n==0)?FSPICS0_OUT_IDX:((n==1)?FSPICS1_OUT_IDX:((n==2)?FSPICS2_OUT_IDX:FSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)    ((p==0)?SPI_SPI_SS_IDX(n):((p==1)?SPI_SPI_SS_IDX(n):((p==2)?SPI_HSPI_SS_IDX(n):0)))

#elif CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
#define SPI_COUNT           (2)

#define SPI_CLK_IDX(p)      ((p==0)?FSPICLK_OUT_IDX:((p==1)?SPI3_CLK_OUT_IDX:0))
#define SPI_MISO_IDX(p)     ((p==0)?FSPIQ_OUT_IDX:((p==1)?SPI3_Q_OUT_IDX:0))
#define SPI_MOSI_IDX(p)     ((p==0)?FSPID_IN_IDX:((p==1)?SPI3_D_IN_IDX:0))

#define SPI_HSPI_SS_IDX(n)  ((n==0)?SPI3_CS0_OUT_IDX:((n==1)?SPI3_CS1_OUT_IDX:0))
#define SPI_FSPI_SS_IDX(n)  ((n==0)?FSPICS0_OUT_IDX:((n==1)?FSPICS1_OUT_IDX:0))
#define SPI_SS_IDX(p, n)    ((p==0)?SPI_FSPI_SS_IDX(n):((p==1)?SPI_HSPI_SS_IDX(n):0))

#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
// ESP32C3
#define SPI_COUNT           (1)

#define SPI_CLK_IDX(p)      FSPICLK_OUT_IDX
#define SPI_MISO_IDX(p)     FSPIQ_OUT_IDX
#define SPI_MOSI_IDX(p)     FSPID_IN_IDX

#define SPI_SPI_SS_IDX(n)   ((n==0)?FSPICS0_OUT_IDX:((n==1)?FSPICS1_OUT_IDX:((n==2)?FSPICS2_OUT_IDX:FSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)    SPI_SPI_SS_IDX(n)

#else
// ESP32
#define SPI_COUNT           (4)

#define SPI_CLK_IDX(p)      ((p==0)?SPICLK_OUT_IDX:((p==1)?SPICLK_OUT_IDX:((p==2)?HSPICLK_OUT_IDX:((p==3)?VSPICLK_OUT_IDX:0))))
#define SPI_MISO_IDX(p)     ((p==0)?SPIQ_OUT_IDX:((p==1)?SPIQ_OUT_IDX:((p==2)?HSPIQ_OUT_IDX:((p==3)?VSPIQ_OUT_IDX:0))))
#define SPI_MOSI_IDX(p)     ((p==0)?SPID_IN_IDX:((p==1)?SPID_IN_IDX:((p==2)?HSPID_IN_IDX:((p==3)?VSPID_IN_IDX:0))))

#define SPI_SPI_SS_IDX(n)   ((n==0)?SPICS0_OUT_IDX:((n==1)?SPICS1_OUT_IDX:((n==2)?SPICS2_OUT_IDX:SPICS0_OUT_IDX)))
#define SPI_HSPI_SS_IDX(n)  ((n==0)?HSPICS0_OUT_IDX:((n==1)?HSPICS1_OUT_IDX:((n==2)?HSPICS2_OUT_IDX:HSPICS0_OUT_IDX)))
#define SPI_VSPI_SS_IDX(n)  ((n==0)?VSPICS0_OUT_IDX:((n==1)?VSPICS1_OUT_IDX:((n==2)?VSPICS2_OUT_IDX:VSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)    ((p==0)?SPI_SPI_SS_IDX(n):((p==1)?SPI_SPI_SS_IDX(n):((p==2)?SPI_HSPI_SS_IDX(n):((p==3)?SPI_VSPI_SS_IDX(n):0))))

#endif

#if CONFIG_DISABLE_HAL_LOCKS
#define SPI_MUTEX_LOCK()
#define SPI_MUTEX_UNLOCK()

static spi_t _spi_bus_array[] = {
#if CONFIG_IDF_TARGET_ESP32S2
    {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), 0, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 1, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 2, -1, -1, -1, -1}
#elif CONFIG_IDF_TARGET_ESP32S3
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 1, -1, -1, -1, -1}
#elif CONFIG_IDF_TARGET_ESP32C3
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1}
#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    {(spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1}
#else
    {(volatile spi_dev_t *)(DR_REG_SPI0_BASE), 0, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), 1, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 2, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 3, -1, -1, -1, -1}
#endif
};
#else
#define SPI_MUTEX_LOCK()    do {} while (xSemaphoreTake(spi->lock, portMAX_DELAY) != pdPASS)
#define SPI_MUTEX_UNLOCK()  xSemaphoreGive(spi->lock)

static spi_t _spi_bus_array[] = {
#if CONFIG_IDF_TARGET_ESP32S2
    {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), NULL, 0, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 1, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 2, -1, -1, -1, -1}
#elif CONFIG_IDF_TARGET_ESP32S3
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 1, -1, -1, -1, -1}
#elif CONFIG_IDF_TARGET_ESP32C3
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1}
#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    {(spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1}
#else
    {(volatile spi_dev_t *)(DR_REG_SPI0_BASE), NULL, 0, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), NULL, 1, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 2, -1, -1, -1, -1},
    {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 3, -1, -1, -1, -1}
#endif
};
#endif

static bool spiDetachBus(void * bus){
    uint8_t spi_num = (int)bus - 1;
    spi_t * spi = &_spi_bus_array[spi_num];
    if(spi->dev->clock.val != 0){
        log_d("Stopping SPI BUS");
        spiStopBus(spi);
    }
    if(spi->sck != -1){
        log_d("SPI detach SCK pin %d",spi->sck);
        spiDetachSCK(spi,spi->sck);
    }
    if(spi->miso != -1){
        log_d("SPI detach MISO pin %d",spi->miso);
        spiDetachMISO(spi,spi->miso);
    }
    if(spi->mosi != -1){
        log_d("SPI detach MOSI pin %d",spi->mosi);
        spiDetachMOSI(spi,spi->mosi);
    }
    if(spi->ss != -1){
        log_d("SPI detach SS pin %d",spi->ss);
        spiDetachSS(spi,spi->ss);
    }
    //set SPI to NULL, as all pins are already detached
    if(spi->sck == -1 && spi->miso == -1 && spi->mosi == -1 && spi->ss == -1){
        log_d("Set spi handle to NULL");
        spi = NULL;
        return true;
    }
    return false;
}


bool spiAttachSCK(spi_t * spi, int8_t sck)
{
    if(!spi || sck < 0) {
        return false;
    }
    void * bus = perimanGetPinBus(sck, ESP32_BUS_TYPE_SPI_MASTER);
    if(bus != NULL && !perimanSetPinBus(sck, ESP32_BUS_TYPE_INIT, NULL)){
        return false;
    }
    pinMode(sck, OUTPUT);
    pinMatrixOutAttach(sck, SPI_CLK_IDX(spi->num), false, false);
    spi->sck = sck;
    if(!perimanSetPinBus(sck, ESP32_BUS_TYPE_SPI_MASTER, (void *)(spi->num+1))){
        spiDetachBus((void *)(spi->num+1));
        log_e("Failed to set pin bus to SPI for pin %d", sck);
        return false;
    }
    return true;
}

bool spiAttachMISO(spi_t * spi, int8_t miso)
{
    if(!spi || miso < 0) {
        return false;
    }
    void * bus = perimanGetPinBus(miso, ESP32_BUS_TYPE_SPI_MASTER);
    if(bus != NULL && !perimanSetPinBus(miso, ESP32_BUS_TYPE_INIT, NULL)){
        return false;
    }
    SPI_MUTEX_LOCK();
    pinMode(miso, INPUT);
    pinMatrixInAttach(miso, SPI_MISO_IDX(spi->num), false);
    spi->miso = miso;
    SPI_MUTEX_UNLOCK();
    if(!perimanSetPinBus(miso, ESP32_BUS_TYPE_SPI_MASTER, (void *)(spi->num+1))){
        spiDetachBus((void *)(spi->num+1));
        log_e("Failed to set pin bus to SPI for pin %d", miso);
        return false;
    }
    return true;
}

bool spiAttachMOSI(spi_t * spi, int8_t mosi)
{
    if(!spi || mosi < 0) {
        return false;
    }
    void * bus = perimanGetPinBus(mosi, ESP32_BUS_TYPE_SPI_MASTER);
    if(bus != NULL && !perimanSetPinBus(mosi, ESP32_BUS_TYPE_INIT, NULL)){
        return false;
    }
    pinMode(mosi, OUTPUT);
    pinMatrixOutAttach(mosi, SPI_MOSI_IDX(spi->num), false, false);
    spi->mosi = mosi;
    if(!perimanSetPinBus(mosi, ESP32_BUS_TYPE_SPI_MASTER, (void *)(spi->num+1))){
        spiDetachBus((void *)(spi->num+1));
        log_e("Failed to set pin bus to SPI for pin %d", mosi);
        return false;
    }
    return true;
}

bool spiDetachSCK(spi_t * spi, int8_t sck)
{
    if(!spi || sck < 0) {
        return false;
    }
    pinMatrixOutDetach(sck, false, false);
    spi->sck = -1;
    perimanSetPinBus(sck, ESP32_BUS_TYPE_INIT, NULL);
    return true;
}

bool spiDetachMISO(spi_t * spi, int8_t miso)
{
    if(!spi || miso < 0) {
        return false;
    }
    pinMatrixInDetach(SPI_MISO_IDX(spi->num), false, false);
    spi->miso = -1;
    perimanSetPinBus(miso, ESP32_BUS_TYPE_INIT, NULL);
    return true;
}

bool spiDetachMOSI(spi_t * spi, int8_t mosi)
{
    if(!spi || mosi < 0) {
        return false;
    }
    pinMatrixOutDetach(mosi, false, false);
    spi->mosi = -1;
    perimanSetPinBus(mosi, ESP32_BUS_TYPE_INIT, NULL);
    return true;
}

bool spiAttachSS(spi_t * spi, uint8_t cs_num, int8_t ss)
{
    if(!spi || ss < 0 || cs_num > 2) {
        return false;
    }
    void * bus = perimanGetPinBus(ss, ESP32_BUS_TYPE_SPI_MASTER);
    if(bus != NULL && !perimanSetPinBus(ss, ESP32_BUS_TYPE_INIT, NULL)){
        return false;
    }
    pinMode(ss, OUTPUT);
    pinMatrixOutAttach(ss, SPI_SS_IDX(spi->num, cs_num), false, false);
    spiEnableSSPins(spi, (1 << cs_num));
    spi->ss = ss;
    if(!perimanSetPinBus(ss, ESP32_BUS_TYPE_SPI_MASTER, (void *)(spi->num+1))){
        spiDetachBus((void *)(spi->num+1));
        log_e("Failed to set pin bus to SPI for pin %d", ss);
        return false;
    }
    return true;
}

bool spiDetachSS(spi_t * spi, int8_t ss)
{
    if(!spi || ss < 0) {
        return false;
    }
    pinMatrixOutDetach(ss, false, false);
    spi->ss = -1;
    perimanSetPinBus(ss, ESP32_BUS_TYPE_INIT, NULL);
    return true;
}

void spiEnableSSPins(spi_t * spi, uint8_t cs_mask)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->misc.val &= ~(cs_mask & SPI_CS_MASK_ALL);
#else
    spi->dev->pin.val &= ~(cs_mask & SPI_CS_MASK_ALL);
#endif
    SPI_MUTEX_UNLOCK();
}

void spiDisableSSPins(spi_t * spi, uint8_t cs_mask)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->misc.val |= (cs_mask & SPI_CS_MASK_ALL);
#else
    spi->dev->pin.val |= (cs_mask & SPI_CS_MASK_ALL);
#endif
    SPI_MUTEX_UNLOCK();
}

void spiSSEnable(spi_t * spi)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    spi->dev->user.cs_setup = 1;
    spi->dev->user.cs_hold = 1;
    SPI_MUTEX_UNLOCK();
}

void spiSSDisable(spi_t * spi)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    spi->dev->user.cs_setup = 0;
    spi->dev->user.cs_hold = 0;
    SPI_MUTEX_UNLOCK();
}

void spiSSSet(spi_t * spi)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->misc.cs_keep_active = 1;
#else
    spi->dev->pin.cs_keep_active = 1;
#endif
    SPI_MUTEX_UNLOCK();
}

void spiSSClear(spi_t * spi)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->misc.cs_keep_active = 0;
#else
    spi->dev->pin.cs_keep_active = 0;
#endif
    SPI_MUTEX_UNLOCK();
}

uint32_t spiGetClockDiv(spi_t * spi)
{
    if(!spi) {
        return 0;
    }
    return spi->dev->clock.val;
}

void spiSetClockDiv(spi_t * spi, uint32_t clockDiv)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    spi->dev->clock.val = clockDiv;
    SPI_MUTEX_UNLOCK();
}

uint8_t spiGetDataMode(spi_t * spi)
{
    if(!spi) {
        return 0;
    }
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    bool idleEdge = spi->dev->misc.ck_idle_edge;
#else
    bool idleEdge = spi->dev->pin.ck_idle_edge;
#endif
    bool outEdge = spi->dev->user.ck_out_edge;
    if(idleEdge) {
        if(outEdge) {
            return SPI_MODE2;
        }
        return SPI_MODE3;
    }
    if(outEdge) {
        return SPI_MODE1;
    }
    return SPI_MODE0;
}

void spiSetDataMode(spi_t * spi, uint8_t dataMode)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    switch (dataMode) {
    case SPI_MODE1:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 0;
#else
        spi->dev->pin.ck_idle_edge = 0;
#endif
        spi->dev->user.ck_out_edge = 1;
        break;
    case SPI_MODE2:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 1;
#else
        spi->dev->pin.ck_idle_edge = 1;
#endif
        spi->dev->user.ck_out_edge = 1;
        break;
    case SPI_MODE3:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 1;
#else
        spi->dev->pin.ck_idle_edge = 1;
#endif
        spi->dev->user.ck_out_edge = 0;
        break;
    case SPI_MODE0:
    default:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 0;
#else
        spi->dev->pin.ck_idle_edge = 0;
#endif
        spi->dev->user.ck_out_edge = 0;
        break;
    }
    SPI_MUTEX_UNLOCK();
}

uint8_t spiGetBitOrder(spi_t * spi)
{
    if(!spi) {
        return 0;
    }
    return (spi->dev->ctrl.wr_bit_order | spi->dev->ctrl.rd_bit_order) == 0;
}

void spiSetBitOrder(spi_t * spi, uint8_t bitOrder)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    if (SPI_MSBFIRST == bitOrder) {
        spi->dev->ctrl.wr_bit_order = 0;
        spi->dev->ctrl.rd_bit_order = 0;
    } else if (SPI_LSBFIRST == bitOrder) {
        spi->dev->ctrl.wr_bit_order = 1;
        spi->dev->ctrl.rd_bit_order = 1;
    }
    SPI_MUTEX_UNLOCK();
}

static void _on_apb_change(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb)
{
    spi_t * spi = (spi_t *)arg;
    if(ev_type == APB_BEFORE_CHANGE){
        SPI_MUTEX_LOCK();
        while(spi->dev->cmd.usr);
    } else {
        spi->dev->clock.val = spiFrequencyToClockDiv(old_apb / ((spi->dev->clock.clkdiv_pre + 1) * (spi->dev->clock.clkcnt_n + 1)));
        SPI_MUTEX_UNLOCK();
    }
}

static void spiInitBus(spi_t * spi)
{
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->slave.trans_done = 0;
#endif
    spi->dev->slave.val = 0;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->misc.val = 0;
#else
    spi->dev->pin.val = 0;
#endif
    spi->dev->user.val = 0;
    spi->dev->user1.val = 0;
    spi->dev->ctrl.val = 0;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->ctrl1.val = 0;
    spi->dev->ctrl2.val = 0;
#else
    spi->dev->clk_gate.val = 0;
    spi->dev->dma_conf.val = 0;
    spi->dev->dma_conf.rx_afifo_rst = 1;
    spi->dev->dma_conf.buf_afifo_rst = 1;
#endif
    spi->dev->clock.val = 0;
}

void spiStopBus(spi_t * spi)
{
    if(!spi) {
        return;
    }

    removeApbChangeCallback(spi, _on_apb_change);

    SPI_MUTEX_LOCK();
    spiInitBus(spi);
    SPI_MUTEX_UNLOCK();
}

spi_t * spiStartBus(uint8_t spi_num, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder)
{
    if(spi_num >= SPI_COUNT){
        return NULL;
    }

    perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER, spiDetachBus);
    spi_t * spi = &_spi_bus_array[spi_num];

#if !CONFIG_DISABLE_HAL_LOCKS
    if(spi->lock == NULL){
        spi->lock = xSemaphoreCreateMutex();
        if(spi->lock == NULL) {
            return NULL;
        }
    }
#endif

#if CONFIG_IDF_TARGET_ESP32S2
    if(spi_num == FSPI) {
        DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN);
        DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST);
    } else if(spi_num == HSPI) {
        DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN);
        DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST);
    } else {
        DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI01_CLK_EN);
        DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI01_RST);
    }
#elif CONFIG_IDF_TARGET_ESP32S3
    if(spi_num == FSPI) {
        periph_ll_reset( PERIPH_SPI2_MODULE );
        periph_ll_enable_clk_clear_rst( PERIPH_SPI2_MODULE );
    } else if(spi_num == HSPI) {
        periph_ll_reset( PERIPH_SPI3_MODULE );
        periph_ll_enable_clk_clear_rst( PERIPH_SPI3_MODULE );
    }
#elif CONFIG_IDF_TARGET_ESP32
    if(spi_num == HSPI) {
        DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN);
        DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST);
    } else if(spi_num == VSPI) {
        DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN);
        DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST);
    } else {
        DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI01_CLK_EN);
        DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI01_RST);
    }
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    periph_ll_reset( PERIPH_SPI2_MODULE );
    periph_ll_enable_clk_clear_rst( PERIPH_SPI2_MODULE );
#endif

    SPI_MUTEX_LOCK();
    spiInitBus(spi);
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->clk_gate.clk_en = 1;
    spi->dev->clk_gate.mst_clk_sel = 1;
    spi->dev->clk_gate.mst_clk_active = 1;
#if !CONFIG_IDF_TARGET_ESP32C6 && !CONFIG_IDF_TARGET_ESP32H2
    spi->dev->dma_conf.tx_seg_trans_clr_en = 1;
    spi->dev->dma_conf.rx_seg_trans_clr_en = 1;
    spi->dev->dma_conf.dma_seg_trans_en = 0;
#endif
#endif
    spi->dev->user.usr_mosi = 1;
    spi->dev->user.usr_miso = 1;
    spi->dev->user.doutdin = 1;
    int i;
    for(i=0; i<16; i++) {
    #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->data_buf[i].val = 0x00000000;
    #else
        spi->dev->data_buf[i] = 0x00000000;
    #endif
    }
    SPI_MUTEX_UNLOCK();

    spiSetDataMode(spi, dataMode);
    spiSetBitOrder(spi, bitOrder);
    spiSetClockDiv(spi, clockDiv);

    addApbChangeCallback(spi, _on_apb_change);

    return spi;
}

void spiWaitReady(spi_t * spi)
{
    if(!spi) {
        return;
    }
    while(spi->dev->cmd.usr);
}

#if CONFIG_IDF_TARGET_ESP32S2
#define usr_mosi_dbitlen usr_mosi_bit_len
#define usr_miso_dbitlen usr_miso_bit_len
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
#define usr_mosi_dbitlen ms_data_bitlen
#define usr_miso_dbitlen ms_data_bitlen
#define mosi_dlen ms_dlen
#define miso_dlen ms_dlen
#endif

void spiWrite(spi_t * spi, const uint32_t *data, uint8_t len)
{
    if(!spi) {
        return;
    }
    int i;
    if(len > 16) {
        len = 16;
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = (len * 32) - 1;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
    for(i=0; i<len; i++) {
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->data_buf[i].val = data[i];
#else
        spi->dev->data_buf[i] = data[i];
#endif
    }
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
    SPI_MUTEX_UNLOCK();
}

void spiTransfer(spi_t * spi, uint32_t *data, uint8_t len)
{
    if(!spi) {
        return;
    }
    int i;
    if(len > 16) {
        len = 16;
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = (len * 32) - 1;
    spi->dev->miso_dlen.usr_miso_dbitlen = (len * 32) - 1;
    for(i=0; i<len; i++) {
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->data_buf[i].val = data[i];
#else
        spi->dev->data_buf[i] = data[i];
#endif
    }
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
    for(i=0; i<len; i++) {
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
       data[i] = spi->dev->data_buf[i].val;
#else
       data[i] = spi->dev->data_buf[i];
#endif
    }
    SPI_MUTEX_UNLOCK();
}

void spiWriteByte(spi_t * spi, uint8_t data)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif

#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
    SPI_MUTEX_UNLOCK();
}

uint8_t spiTransferByte(spi_t * spi, uint8_t data)
{
    if(!spi) {
        return 0;
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
    spi->dev->miso_dlen.usr_miso_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val & 0xFF;
#else
    data = spi->dev->data_buf[0] & 0xFF;
#endif
    SPI_MUTEX_UNLOCK();
    return data;
}

static uint32_t __spiTranslate32(uint32_t data)
{
    union {
        uint32_t l;
        uint8_t b[4];
    } out;
    out.l = data;
    return out.b[3] | (out.b[2] << 8) | (out.b[1] << 16) | (out.b[0] << 24);
}

void spiWriteWord(spi_t * spi, uint16_t data)
{
    if(!spi) {
        return;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        data = (data >> 8) | (data << 8);
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
    SPI_MUTEX_UNLOCK();
}

uint16_t spiTransferWord(spi_t * spi, uint16_t data)
{
    if(!spi) {
        return 0;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        data = (data >> 8) | (data << 8);
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
    spi->dev->miso_dlen.usr_miso_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val;
#else
    data = spi->dev->data_buf[0];
#endif
    SPI_MUTEX_UNLOCK();
    if(!spi->dev->ctrl.rd_bit_order){
        data = (data >> 8) | (data << 8);
    }
    return data;
}

void spiWriteLong(spi_t * spi, uint32_t data)
{
    if(!spi) {
        return;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        data = __spiTranslate32(data);
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
    SPI_MUTEX_UNLOCK();
}

uint32_t spiTransferLong(spi_t * spi, uint32_t data)
{
    if(!spi) {
        return 0;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        data = __spiTranslate32(data);
    }
    SPI_MUTEX_LOCK();
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
    spi->dev->miso_dlen.usr_miso_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val;
#else
    data = spi->dev->data_buf[0];
#endif
    SPI_MUTEX_UNLOCK();
    if(!spi->dev->ctrl.rd_bit_order){
        data = __spiTranslate32(data);
    }
    return data;
}

static void __spiTransferBytes(spi_t * spi, const uint8_t * data, uint8_t * out, uint32_t bytes)
{
    if(!spi) {
        return;
    }
    uint32_t i;

    if(bytes > 64) {
        bytes = 64;
    }

    uint32_t words = (bytes + 3) / 4;//16 max

    uint32_t wordsBuf[16] = {0,};
    uint8_t * bytesBuf = (uint8_t *) wordsBuf;

    if(data) {
        memcpy(bytesBuf, data, bytes);//copy data to buffer
    } else {
        memset(bytesBuf, 0xFF, bytes);
    }

    spi->dev->mosi_dlen.usr_mosi_dbitlen = ((bytes * 8) - 1);
    spi->dev->miso_dlen.usr_miso_dbitlen = ((bytes * 8) - 1);

    for(i=0; i<words; i++) {
    #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->data_buf[i].val = wordsBuf[i];    //copy buffer to spi fifo
    #else
        spi->dev->data_buf[i] = wordsBuf[i];    //copy buffer to spi fifo
    #endif
    }

#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;

    while(spi->dev->cmd.usr);

    if(out) {
        for(i=0; i<words; i++) {
        #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
            wordsBuf[i] = spi->dev->data_buf[i].val;//copy spi fifo to buffer
        #else
            wordsBuf[i] = spi->dev->data_buf[i];//copy spi fifo to buffer
        #endif
        }
        memcpy(out, bytesBuf, bytes);//copy buffer to output
    }
}

void spiTransferBytes(spi_t * spi, const uint8_t * data, uint8_t * out, uint32_t size)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    while(size) {
        if(size > 64) {
            __spiTransferBytes(spi, data, out, 64);
            size -= 64;
            if(data) {
                data += 64;
            }
            if(out) {
                out += 64;
            }
        } else {
            __spiTransferBytes(spi, data, out, size);
            size = 0;
        }
    }
    SPI_MUTEX_UNLOCK();
}

void spiTransferBits(spi_t * spi, uint32_t data, uint32_t * out, uint8_t bits)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    spiTransferBitsNL(spi, data, out, bits);
    SPI_MUTEX_UNLOCK();
}

/*
 * Manual Lock Management
 * */

#define MSB_32_SET(var, val) { uint8_t * d = (uint8_t *)&(val); (var) = d[3] | (d[2] << 8) | (d[1] << 16) | (d[0] << 24); }
#define MSB_24_SET(var, val) { uint8_t * d = (uint8_t *)&(val); (var) = d[2] | (d[1] << 8) | (d[0] << 16); }
#define MSB_16_SET(var, val) { (var) = (((val) & 0xFF00) >> 8) | (((val) & 0xFF) << 8); }
#define MSB_PIX_SET(var, val) { uint8_t * d = (uint8_t *)&(val); (var) = d[1] | (d[0] << 8) | (d[3] << 16) | (d[2] << 24); }

void spiTransaction(spi_t * spi, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
    spi->dev->clock.val = clockDiv;
    switch (dataMode) {
    case SPI_MODE1:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 0;
#else
        spi->dev->pin.ck_idle_edge = 0;
#endif
        spi->dev->user.ck_out_edge = 1;
        break;
    case SPI_MODE2:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 1;
#else
        spi->dev->pin.ck_idle_edge = 1;
#endif
        spi->dev->user.ck_out_edge = 1;
        break;
    case SPI_MODE3:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 1;
#else
        spi->dev->pin.ck_idle_edge = 1;
#endif
        spi->dev->user.ck_out_edge = 0;
        break;
    case SPI_MODE0:
    default:
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->misc.ck_idle_edge = 0;
#else
        spi->dev->pin.ck_idle_edge = 0;
#endif
        spi->dev->user.ck_out_edge = 0;
        break;
    }
    if (SPI_MSBFIRST == bitOrder) {
        spi->dev->ctrl.wr_bit_order = 0;
        spi->dev->ctrl.rd_bit_order = 0;
    } else if (SPI_LSBFIRST == bitOrder) {
        spi->dev->ctrl.wr_bit_order = 1;
        spi->dev->ctrl.rd_bit_order = 1;
    }
}

void spiSimpleTransaction(spi_t * spi)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_LOCK();
}

void spiEndTransaction(spi_t * spi)
{
    if(!spi) {
        return;
    }
    SPI_MUTEX_UNLOCK();
}

void ARDUINO_ISR_ATTR spiWriteByteNL(spi_t * spi, uint8_t data)
{
    if(!spi) {
        return;
    }
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
}

uint8_t spiTransferByteNL(spi_t * spi, uint8_t data)
{
    if(!spi) {
        return 0;
    }
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
    spi->dev->miso_dlen.usr_miso_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val & 0xFF;
#else
    data = spi->dev->data_buf[0] & 0xFF;
#endif
    return data;
}

void ARDUINO_ISR_ATTR spiWriteShortNL(spi_t * spi, uint16_t data)
{
    if(!spi) {
        return;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        MSB_16_SET(data, data);
    }
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
}

uint16_t spiTransferShortNL(spi_t * spi, uint16_t data)
{
    if(!spi) {
        return 0;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        MSB_16_SET(data, data);
    }
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
    spi->dev->miso_dlen.usr_miso_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val & 0xFFFF;
#else
    data = spi->dev->data_buf[0] & 0xFFFF;
#endif
    if(!spi->dev->ctrl.rd_bit_order){
        MSB_16_SET(data, data);
    }
    return data;
}

void ARDUINO_ISR_ATTR spiWriteLongNL(spi_t * spi, uint32_t data)
{
    if(!spi) {
        return;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        MSB_32_SET(data, data);
    }
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
}

uint32_t spiTransferLongNL(spi_t * spi, uint32_t data)
{
    if(!spi) {
        return 0;
    }
    if(!spi->dev->ctrl.wr_bit_order){
        MSB_32_SET(data, data);
    }
    spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
    spi->dev->miso_dlen.usr_miso_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
 #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val;
#else
    data = spi->dev->data_buf[0];
#endif
    if(!spi->dev->ctrl.rd_bit_order){
        MSB_32_SET(data, data);
    }
    return data;
}

void spiWriteNL(spi_t * spi, const void * data_in, uint32_t len){
    if(!spi) {
        return;
    }
    size_t longs = len >> 2;
    if(len & 3){
        longs++;
    }
    uint32_t * data = (uint32_t*)data_in;
    size_t c_len = 0, c_longs = 0;

    while(len){
        c_len = (len>64)?64:len;
        c_longs = (longs > 16)?16:longs;

        spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len*8)-1;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
        for (size_t i=0; i<c_longs; i++) {
        #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
            spi->dev->data_buf[i].val = data[i];
        #else
            spi->dev->data_buf[i] = data[i];
        #endif
        }
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->cmd.update = 1;
        while (spi->dev->cmd.update);
#endif
        spi->dev->cmd.usr = 1;
        while(spi->dev->cmd.usr);

        data += c_longs;
        longs -= c_longs;
        len -= c_len;
    }
}

void spiTransferBytesNL(spi_t * spi, const void * data_in, uint8_t * data_out, uint32_t len){
    if(!spi) {
        return;
    }
    size_t longs = len >> 2;
    if(len & 3){
        longs++;
    }
    uint32_t * data = (uint32_t*)data_in;
    uint32_t * result = (uint32_t*)data_out;
    size_t c_len = 0, c_longs = 0;

    while(len){
        c_len = (len>64)?64:len;
        c_longs = (longs > 16)?16:longs;

        spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len*8)-1;
        spi->dev->miso_dlen.usr_miso_dbitlen = (c_len*8)-1;
        if(data){
            for (size_t i=0; i<c_longs; i++) {
            #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                spi->dev->data_buf[i].val = data[i];
            #else
                spi->dev->data_buf[i] = data[i];
            #endif
            }
        } else {
            for (size_t i=0; i<c_longs; i++) {
            #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                spi->dev->data_buf[i].val = 0xFFFFFFFF;
            #else
                spi->dev->data_buf[i] = 0xFFFFFFFF;
            #endif
            }
        }
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->cmd.update = 1;
        while (spi->dev->cmd.update);
#endif
        spi->dev->cmd.usr = 1;
        while(spi->dev->cmd.usr);
        if(result){
            if(c_len & 3){
                for (size_t i=0; i<(c_longs-1); i++) {
                #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                    result[i] = spi->dev->data_buf[i].val;
                #else
                    result[i] = spi->dev->data_buf[i];
                #endif
                }
                #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                    uint32_t last_data = spi->dev->data_buf[c_longs-1].val;
                #else
                    uint32_t last_data = spi->dev->data_buf[c_longs-1];
                #endif
                uint8_t * last_out8 = (uint8_t *)&result[c_longs-1];
                uint8_t * last_data8 = (uint8_t *)&last_data;
                for (size_t i=0; i<(c_len & 3); i++) {
                    last_out8[i] = last_data8[i];
                }
            } else {
                for (size_t i=0; i<c_longs; i++) {
                #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                    result[i] = spi->dev->data_buf[i].val;
                #else
                    result[i] = spi->dev->data_buf[i];
                #endif
                }
            }
        }
        if(data){
            data += c_longs;
        }
        if(result){
            result += c_longs;
        }
        longs -= c_longs;
        len -= c_len;
    }
}

void spiTransferBitsNL(spi_t * spi, uint32_t data, uint32_t * out, uint8_t bits)
{
    if(!spi) {
        return;
    }

    if(bits > 32) {
        bits = 32;
    }
    uint32_t bytes = (bits + 7) / 8;//64 max
    uint32_t mask = (((uint64_t)1 << bits) - 1) & 0xFFFFFFFF;
    data = data & mask;
    if(!spi->dev->ctrl.wr_bit_order){
        if(bytes == 2) {
            MSB_16_SET(data, data);
        } else if(bytes == 3) {
            MSB_24_SET(data, data);
        } else {
            MSB_32_SET(data, data);
        }
    }

    spi->dev->mosi_dlen.usr_mosi_dbitlen = (bits - 1);
    spi->dev->miso_dlen.usr_miso_dbitlen = (bits - 1);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->data_buf[0].val = data;
#else
    spi->dev->data_buf[0] = data;
#endif
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while(spi->dev->cmd.usr);
 #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    data = spi->dev->data_buf[0].val;
#else
    data = spi->dev->data_buf[0];
#endif
    if(out) {
        *out = data;
        if(!spi->dev->ctrl.rd_bit_order){
            if(bytes == 2) {
                MSB_16_SET(*out, data);
            } else if(bytes == 3) {
                MSB_24_SET(*out, data);
            } else {
                MSB_32_SET(*out, data);
            }
        }
    }
}

void ARDUINO_ISR_ATTR spiWritePixelsNL(spi_t * spi, const void * data_in, uint32_t len){
    size_t longs = len >> 2;
    if(len & 3){
        longs++;
    }
    bool msb = !spi->dev->ctrl.wr_bit_order;
    uint32_t * data = (uint32_t*)data_in;
    size_t c_len = 0, c_longs = 0, l_bytes = 0;

    while(len){
        c_len = (len>64)?64:len;
        c_longs = (longs > 16)?16:longs;
        l_bytes = (c_len & 3);

        spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len*8)-1;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
        for (size_t i=0; i<c_longs; i++) {
            if(msb){
                if(l_bytes && i == (c_longs - 1)){
                    if(l_bytes == 2){
                    #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                        MSB_16_SET(spi->dev->data_buf[i].val, data[i]);
                    #else
                        MSB_16_SET(spi->dev->data_buf[i], data[i]);
                    #endif
                    } else {
                    #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                        spi->dev->data_buf[i].val = data[i] & 0xFF;
                    #else
                        spi->dev->data_buf[i] = data[i] & 0xFF;
                    #endif
                    }
                } else {
                    #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                        MSB_PIX_SET(spi->dev->data_buf[i].val, data[i]);
                    #else
                        MSB_PIX_SET(spi->dev->data_buf[i], data[i]);
                    #endif
                }
            } else {
            #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
                spi->dev->data_buf[i].val = data[i];
            #else
                spi->dev->data_buf[i] = data[i];
            #endif
            }
        }
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
        spi->dev->cmd.update = 1;
        while (spi->dev->cmd.update);
#endif
        spi->dev->cmd.usr = 1;
        while(spi->dev->cmd.usr);

        data += c_longs;
        longs -= c_longs;
        len -= c_len;
    }
}



/*
 * Clock Calculators
 *
 * */

typedef union {
    uint32_t value;
    struct {
            uint32_t clkcnt_l:       6;                     /*it must be equal to spi_clkcnt_N.*/
            uint32_t clkcnt_h:       6;                     /*it must be floor((spi_clkcnt_N+1)/2-1).*/
            uint32_t clkcnt_n:       6;                     /*it is the divider of spi_clk. So spi_clk frequency is system/(spi_clkdiv_pre+1)/(spi_clkcnt_N+1)*/
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
            uint32_t clkdiv_pre:     4;                     /*it is pre-divider of spi_clk.*/
            uint32_t reserved:       9;  					/*reserved*/
#else
            uint32_t clkdiv_pre:    13;                     /*it is pre-divider of spi_clk.*/
#endif
            uint32_t clk_equ_sysclk: 1;                     /*1: spi_clk is eqaul to system 0: spi_clk is divided from system clock.*/
    };
} spiClk_t;

#define ClkRegToFreq(reg) (apb_freq / (((reg)->clkdiv_pre + 1) * ((reg)->clkcnt_n + 1)))

uint32_t spiClockDivToFrequency(uint32_t clockDiv)
{
    uint32_t apb_freq = getApbFrequency();
    spiClk_t reg = { clockDiv };
    return ClkRegToFreq(&reg);
}

uint32_t spiFrequencyToClockDiv(uint32_t freq)
{
    uint32_t apb_freq = getApbFrequency();

    if(freq >= apb_freq) {
        return SPI_CLK_EQU_SYSCLK;
    }

    const spiClk_t minFreqReg = { 0x7FFFF000 };
    uint32_t minFreq = ClkRegToFreq((spiClk_t*) &minFreqReg);
    if(freq < minFreq) {
        return minFreqReg.value;
    }

    uint8_t calN = 1;
    spiClk_t bestReg = { 0 };
    uint32_t bestFreq = 0;

    while(calN <= 0x3F) {
        spiClk_t reg = { 0 };
        uint32_t calFreq;
        int32_t calPre;
        int8_t calPreVari = -2;

        reg.clkcnt_n = calN;

        while(calPreVari++ <= 1) {
            calPre = (((apb_freq / (reg.clkcnt_n + 1)) / freq) - 1) + calPreVari;
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
            if(calPre > 0xF) {
                reg.clkdiv_pre = 0xF;
#else
            if(calPre > 0x1FFF) {
                reg.clkdiv_pre = 0x1FFF;
#endif
            } else if(calPre <= 0) {
                reg.clkdiv_pre = 0;
            } else {
                reg.clkdiv_pre = calPre;
            }
            reg.clkcnt_l = ((reg.clkcnt_n + 1) / 2);
            calFreq = ClkRegToFreq(&reg);
            if(calFreq == freq) {
                memcpy(&bestReg, &reg, sizeof(bestReg));
                break;
            } else if(calFreq < freq) {
                if((freq - calFreq) < (freq - bestFreq)) {
                    bestFreq = calFreq;
                    memcpy(&bestReg, &reg, sizeof(bestReg));
                }
            }
        }
        if(calFreq == (int32_t) freq) {
            break;
        }
        calN++;
    }
    return bestReg.value;
}

#endif /* SOC_GPSPI_SUPPORTED */
